#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/miscdevice.h>

#include <linux/slab.h>

#include "my_common.h"

#define PAGE_COUNT 1
static struct page *pg;

static int mydrv_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	unsigned long size = vma->vm_end - vma->vm_start;
	unsigned long pfn;

	if (size > PAGE_COUNT * PAGE_SIZE) {
		pr_err("size beyond reserved pages\n");
		return -ENOMEM;
	}

	pfn = page_to_pfn(pg);
	ret = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
	if (ret) {
		pr_err("remap_pfn_range failed\n");
		return ret;
	}

	return 0;
}


static long mydrv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int *buf;

	switch (cmd) {
	case CMD1:
		buf = (int*)page_to_virt(pg);
		pr_info("Kernel: data=0x%x\n", *buf);
		break;
	default:
		pr_err("Doesn't support cmd %d\n", cmd);
	}

	return 0;
}



static struct file_operations mydrv_fops = {
	.owner = THIS_MODULE,
	.mmap = mydrv_mmap,
	.unlocked_ioctl = mydrv_ioctl,
};


static struct miscdevice mydrv_misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "mydev",
	.fops = &mydrv_fops,
};


static int __init mydrv_init(void)
{
	int ret;

	pr_info("%s\n", __func__);

	pg = alloc_pages(GFP_KERNEL, PAGE_COUNT >> 2);
	if (!pg) {
		pr_err("alloc_pages failed\n");
		return -ENOMEM;
	}

	ret = misc_register(&mydrv_misc_dev);
	if (ret) {
		pr_err("%s: misc_register failed, errno=%d\n", __func__, ret);
		__free_pages(pg, PAGE_COUNT >> 2);
		pg = NULL;
		return ret;
	}

	return 0;
}

static void __exit mydrv_exit(void)
{
	pr_info("%s\n", __func__);

	if (pg)
		__free_pages(pg, PAGE_COUNT >> 2);

	misc_deregister(&mydrv_misc_dev);
}

module_init(mydrv_init);
module_exit(mydrv_exit);

MODULE_AUTHOR("mydrv");
MODULE_LICENSE("GPL");
